package cn.wen.core.eventbus;

import android.os.Handler;
import android.os.Looper;
import android.support.v4.util.ArrayMap;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 访开源项目，Android系统优化的事件订阅总线
 *
 * @author liufuwen
 */

public class EventBus {
    static volatile EventBus mInstance;
    /**
     * key: 接收事件 的方法的参数类型
     * value: 接收事件对象和方法的封装
     */
    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
    /**
     * 根据调用对象：如Activity Fragment来缓存接收事件 的方法的参数类型
     */
    private final Map<Object, List<Class<?>>> typesBySubscriber;
    /**
     * 缓存封装的Method，防止多次注册带来一些问题
     */
    private final Map<Class<?>, List<SubscriberMethod>> methodList;
    private final Map<Class<?>, Object> stickyEvents;
    /**
     * 到主线各执行的Handler
     */
    private final Handler mHandler;
    /**
     * 线程池
     */
    private final ExecutorService DEFAULT_EXECUTOR_SERVICE;

    private EventBus() {
        subscriptionsByEventType = new ArrayMap<>();
        typesBySubscriber = new ArrayMap<>();
        stickyEvents = new ArrayMap<>();
        methodList = new ArrayMap<>();
        mHandler = new Handler(Looper.getMainLooper());
        DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
    }

    public static EventBus getDefault() {
        if (mInstance == null) {
            synchronized (EventBus.class) {
                if (mInstance == null) {
                    mInstance = new EventBus();
                }
            }
        }
        return mInstance;
    }

    /**
     * 绑定
     */
    public void register(Object object) {
        if (object == null) {
            return;
        }
        Class<?> subscriberClass = object.getClass();
        // 1. 通过 反射找到类里面所有接收事件 的方法
        List<SubscriberMethod> subscriberMethods = findSubscriberMethods(subscriberClass);
        if (subscriberMethods.size() <= 0) {
            return;
        }
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(object, subscriberMethod);
            }
        }

    }

    /**
     * 通过 反射找到类里面所有接收事件 的方法
     */
    private List<SubscriberMethod> findSubscriberMethods(Class<?> objClass) {
        List<SubscriberMethod> list = methodList.get(objClass);
        if (list == null) {
            list = new ArrayList<>();
        }
        // 1. 通过 反射找到类里面所有的方法
        Method[] methods = objClass.getDeclaredMethods();
        for (Method method : methods) {
            Subscribe subscribe = method.getAnnotation(Subscribe.class);
           /// int modifiers = method.getModifiers(); &&(modifiers & Modifier.PUBLIC) != 0
            if (subscribe != null ) {
                // 获取方法里面所有的参数类型
                Class<?>[] types = method.getParameterTypes();
                // 获取只有一个参数的方法
                if (types != null && types.length == 1) {
                    SubscriberMethod subscriberMethod = new SubscriberMethod(method, types[0],
                            subscribe.threadMode(), subscribe.priority(), subscribe.sticky());
                    if (list.contains(subscriberMethod)) {
                        continue;
                    }
                    list.add(subscriberMethod);
                }

            }
        }
        return list;
    }

    /**
     * 1.对接收事件的类的方法进行一个封装
     * 2.对粘滞事件进行的一个处理
     */
    private void subscribe(Object object, SubscriberMethod subscriberMethod) {
        // 1.通过Key来获取封装对象的集合
        Class<?> eventType = subscriberMethod.eventType;
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        }

        Subscription newSubscription = new Subscription(object, subscriberMethod);
        // 2. 这个是对优先级进行排序并添加到集合
        int size = subscriptions.size();
        for (int i = 0; i <= size; i++) {
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                if (!subscriptions.contains(newSubscription)) {
                    subscriptions.add(i, newSubscription);
                }
                break;
            }
        }

        // 3. 根据调用对象：如Activity Fragment来缓存接收事件 的方法的参数类型
        List<Class<?>> subscribedEvents = typesBySubscriber.get(object);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(object, subscribedEvents);
        }
        if (!subscribedEvents.contains(eventType)) {
            subscribedEvents.add(eventType);
        }


        // TODO 4. 粘滞事件的处理
        if (subscriberMethod.sticky) {
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }

    }

    /**
     * 发送粘滞事件
     */
    public void postSticky(Object event) {
        if (event == null) {
            return;
        }
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        post(event);
    }

    /**
     * 根据参数获取粘滞事件
     */
    public <T> T getStickyEvent(Class<T> eventType) {
        synchronized (stickyEvents) {
            return eventType.cast(stickyEvents.get(eventType));
        }
    }

    /**
     * 根据参数类型移除粘滞事件
     */
    public <T> T removeStickyEvent(Class<T> eventType) {
        synchronized (stickyEvents) {
            return eventType.cast(stickyEvents.remove(eventType));
        }
    }

    /**
     * 移除粘滞事件
     */
    public boolean removeStickyEvent(Object event) {
        synchronized (stickyEvents) {
            Class<?> eventType = event.getClass();
            Object existingEvent = stickyEvents.get(eventType);
            if (event.equals(existingEvent)) {
                stickyEvents.remove(eventType);
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * 移除所有的粘滞事件
     */
    public void removeAllStickyEvents() {
        synchronized (stickyEvents) {
            stickyEvents.clear();
        }
    }

    private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            postToSubscription(newSubscription, stickyEvent, isMainThread());
        }
    }

    /**
     * 解绑定
     */
    public synchronized void unregister(Object object) {
        if (object == null) {
            return;
        }
        // 根据调用对象：如Activity Fragment来缓存接收事件 的方法的参数类型
        List<Class<?>> eventTypes = typesBySubscriber.get(object);
        if (eventTypes != null) {
            for (Class<?> eventType : eventTypes) {
                unsubscribeByEventType(object, eventType);
            }
            // 移除根据调用对象来缓存接收事件的方法的参数类型
            typesBySubscriber.remove(object);
            methodList.remove(object.getClass());
        }
    }

    /**
     * 移除接收事件对象和方法的封装
     */
    private void unsubscribeByEventType(Object object, Class<?> eventType) {
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions != null) {
            int size = subscriptions.size();
            for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                if (subscription.subscriber == object) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }

    /**
     * 发送事件
     */
    public void post(Object event) {
        if (event == null) {
            return;
        }
        // 参数类型
        Class<?> eventClass = event.getClass();
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null) {
            for (Subscription subscription : subscriptions) {
                postToSubscription(subscription, event, isMainThread());
            }
        }
    }

    /**
     * 执行调用方法的一个任务
     */
    private class InvokeTask implements Runnable {
        Subscription subscription;
        Object event;

        public InvokeTask(Subscription subscription, Object event) {
            this.subscription = subscription;
            this.event = event;
        }

        @Override
        public void run() {
            invokeMethod(subscription, event);
        }
    }

    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeMethod(subscription, event);
                break;
            case MAIN:
                if (isMainThread) {
                    invokeMethod(subscription, event);
                } else {
                    // 到主线程去执行
                    mHandler.post(new InvokeTask(subscription, event));
                }
                break;

            case BACKGROUND:
                if (!isMainThread) {
                    invokeMethod(subscription, event);
                } else {
                    // 到子线程去执行
                    DEFAULT_EXECUTOR_SERVICE.execute(new InvokeTask(subscription, event));
                }
                break;
            case ASYNC:
                // 去另一个线程去执行
                DEFAULT_EXECUTOR_SERVICE.execute(new InvokeTask(subscription, event));
                break;
            default:
                break;
        }
    }

    private void invokeMethod(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.setAccessible(true);
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public boolean isMainThread() {
        return Looper.getMainLooper() == Looper.myLooper();
    }
}
